package com.hzqiuxm.tdddemo001.junit5demo.mokito;

import com.hzqiuxm.tdddemo001.junit5demo.coverage.Calculator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;


/**
 * Copyright © 2021年 tdd-demo-001. All rights reserved.
 *
 * @author 临江仙 hzqiuxm@163.com
 * Mokito基本使用
 * 1 @Mock：用于模拟的创建，使得测试类更具可读性（不调用真实方式，默认返回都是null）
 * 2 @Spy：用于创建间谍实例，代替spy(Object)方法(调用真实方式)
 * 3 @InjectMocks：用于自动实例化测试对象，可以调用真实的方法，当前类中所有其它带有@Mock或@Spy注解的字段会被注入到该实例中（类似Spring框架中自动注入）
 * 4 @Captor：用于创建参数捕获器
 * @date 2021/2/23 10:34
 */
@DisplayName("Mockito测试基本功能示例")
public class BaseUse {

    @DisplayName("配置mock对象测试")
    @Test
    void test1_thenReturn() {
        List mock = mock(List.class);
        when(mock.get(0)).thenReturn("临江仙");

        assertEquals( "临江仙", mock.get(0),"預期返回临江仙");
    }

    @DisplayName("迭代风格的返回测试")
    @Test
    void test2_iterator(){
        Iterator it = mock(Iterator.class);
        when(it.next()).thenReturn("江湖").thenReturn("极客");

        assertEquals("江湖", it.next());
        assertEquals("极客", it.next());
    }


    @DisplayName("无返回值时测试")
    @Test
    void test3_noReturnValue(){
        List list = new LinkedList();
        List spy = spy(list);

        // 当spy调用clear()的时候，不执行任何东西
        doNothing().when(spy).clear();

        spy.add("临江仙");
        //调用了clear()，不执行任何东西，所以spy里的容量还是1
        spy.clear();

        assertEquals(1, spy.size());
    }


    @DisplayName("抛出异常测试")
    @Test
    void test4_throwException(){
        Iterator it = mock(Iterator.class);
        // it.next()会抛出异常
        when(it.next()).thenThrow(new RuntimeException("Next Exception"));
        // it.remove()也抛出异常
        doThrow(new RuntimeException("Remove Exception")).when(it).remove();

        // 验证异常it.next()
        Throwable nextException = assertThrows(RuntimeException.class, ()->{
            it.next();
        });
        assertEquals("Next Exception", nextException.getMessage());

        // 验证异常it.remove()
        Throwable removeException = assertThrows(RuntimeException.class, ()->{
            it.remove();
        });
        assertEquals("Remove Exception", removeException.getMessage());
    }

    @DisplayName("模拟参数传入测试")
    @Test
    void test5_param() {
        List list = mock(List.class);
        when(list.get(1)).thenReturn("临江仙");
        assertEquals("临江仙", list.get(1));
        assertEquals(null, list.get(999));

        // 可以用anyInt()表示任意整数
        when(list.remove(anyInt())).thenReturn("极客江湖");
        assertEquals("极客江湖", list.remove(3));
        assertEquals("极客江湖", list.remove(66));

        // 可以用any(class)表示任意对象
        when(list.remove(any(Calculator.class))).thenReturn(true);
        assertEquals(true, list.remove(new Calculator()));
    }

    @DisplayName("thenAnswer动态返回结果测试")
    @Test
    void test6_thenAnswer() {
        List list = mock(List.class);

        Answer answer = new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                // 获取参数
                Object[] args = invocation.getArguments();
                // 获取方法
                Method method = invocation.getMethod();
                // 获取mock对象类名：List$MockitoMock$1159026000
                String mockClsName = invocation.getMock().getClass().getSimpleName().substring(0, 4);

                return mockClsName + "." + method.getName() + "() 传入参数 " + Arrays.toString(args);
            }

        };

        when(list.get(anyInt())).thenAnswer(answer);
        System.out.println(list.get(1));
        System.out.println(list.get(2));
        System.out.println(list.get(3));

    }

}
